package is.glif.fenius.client.draft;

import is.glif.fenius.client.draft.beans.ClientConfig;
import is.glif.fenius.client.draft.beans.Request;
import is.glif.fenius.client.draft.beans.RequestSet;
import is.glif.fenius.client.draft.beans.ServerInfo;
import is.glif.fenius.ws.draft.gen.Avp;
import is.glif.fenius.ws.draft.gen.AvpSeq;
import is.glif.fenius.ws.draft.gen.CommonParams;
import is.glif.fenius.ws.draft.gen.Create;
import is.glif.fenius.ws.draft.gen.CreateReq;
import is.glif.fenius.ws.draft.gen.CreateResp;
import is.glif.fenius.ws.draft.gen.CreateResponse;
import is.glif.fenius.ws.draft.gen.Draft;
import is.glif.fenius.ws.draft.gen.DraftFeniusService;
import is.glif.fenius.ws.draft.gen.EndpointPair;
import is.glif.fenius.ws.draft.gen.ExternalFault_Exception;
import is.glif.fenius.ws.draft.gen.InternalFault_Exception;
import is.glif.fenius.ws.draft.gen.ListReq;
import is.glif.fenius.ws.draft.gen.ListResponse;
import is.glif.fenius.ws.draft.gen.ReservationParams;
import is.glif.fenius.ws.draft.gen.Status;
import is.glif.fenius.ws.draft.gen.TimeParams;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;

import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.configuration.security.FiltersType;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;

import com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl;

public class Requester {

    private static final QName SERVICE_NAME = new QName("http://fenius.glif.is/ws/draft/", "draft");

    private ClientConfig clientConfig;

    private Operation operation;
    private String requestSetId = null;
    private String reservationId = null;
    private String serverId = null;
    private DraftFeniusService port = null;
    private HashMap<String, ReservationParams> reservations;

    public enum Operation {
        CREATE, LIST, CANCEL;
    }
    public Requester() {
        reservations = new HashMap<String, ReservationParams>();
    }


    public void start() {
        if (operation == null) {
            System.out.println("No operation set.");
            return;
        } else if (operation.equals(Operation.CREATE)) {
            this.create();
        } else if (operation.equals(Operation.CANCEL)) {
            this.cancel();
        } else if (operation.equals(Operation.LIST)) {
            this.list();
        }
    }


    private void initSoap(String serverId) {
        if (clientConfig == null) {
            System.err.println("No client config");
            System.exit(1);
        }
        if (clientConfig.getServerInfo(serverId) == null) {
            System.err.println("No server info for serverId "+serverId);
            System.exit(1);
        }
        
        
        if (clientConfig.isUseSSL()) {
            System.setProperty("javax.net.ssl.trustStoreType", "jks");  
            System.setProperty("javax.net.ssl.trustStore", clientConfig.getSslTruststorePath());  
            System.setProperty("javax.net.ssl.trustStorePassword", clientConfig.getSslTruststorePass());  
              
            System.setProperty("javax.net.ssl.keyStoreType", "jks");  
            System.setProperty("javax.net.ssl.keyStore", clientConfig.getSslKeystorePath());  
            System.setProperty("javax.net.ssl.keyStorePassword", clientConfig.getSslKeystorePass());  
        }
        


        String wsdlLocation = clientConfig.getServerInfo(serverId).getAddress();

        URL wsdlURL = null;
        try {
            wsdlURL = new URL(wsdlLocation);
        } catch (MalformedURLException e1) {
            e1.printStackTrace();
            System.exit(1);
        }
        System.out.println(wsdlLocation);

        
        
        
        Draft ss = new Draft(wsdlURL, SERVICE_NAME);
        port = ss.getDraftFeniusServicePort();
        
        org.apache.cxf.endpoint.Client client = ClientProxy.getClient(port); 
        HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
        HTTPClientPolicy cp = new HTTPClientPolicy();
        Integer timeout = clientConfig.getHttpTimeout();
        Long connTO = new Long(timeout);
        Long rcvTO = new Long(timeout);
        
        cp.setConnectionTimeout(connTO);
        cp.setReceiveTimeout(rcvTO);
        httpConduit.setClient(cp);
        if (clientConfig.isUseSSL()) {
            configureSSLOnTheClient(port, clientConfig);
        }
        if (clientConfig.isUseBasicHttpAuth()) {
            BindingProvider bp = (BindingProvider) port;
            bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, clientConfig.getHttpUsername());
            bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, clientConfig.getHttpPassword());

            
        }
    }
    
    
    
    private static void configureSSLOnTheClient(Object c, ClientConfig clientConfig) { 
        org.apache.cxf.endpoint.Client client = ClientProxy.getClient(c); 
        HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
 
        TLSClientParameters tlsParams = new TLSClientParameters(); 
        tlsParams.setDisableCNCheck(true); 
        FiltersType filter = new FiltersType(); 
        filter.getInclude().add(".*_EXPORT_.*"); 
        filter.getInclude().add(".*_EXPORT1024_.*"); 
        filter.getInclude().add(".*_WITH_DES_.*"); 
        filter.getInclude().add(".*_WITH_NULL_.*"); 
        filter.getExclude().add(".*_DH_anon_.*"); 
        tlsParams.setCipherSuitesFilter(filter); 
        
        httpConduit.setTlsClientParameters(tlsParams); 
    } 
    
    
    
    private void create() {
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
        GregorianCalendar now = (GregorianCalendar) GregorianCalendar.getInstance();
        df.setTimeZone(TimeZone.getTimeZone("GMT"));

        System.out.println("Processing request set: "+requestSetId);
        String reservationId = "fenius-"+df.format(now.getTime());
        RequestSet rs = clientConfig.getRequestSet(requestSetId);

        String strBw = rs.getBandwidth();
        Integer bandwidth = Integer.valueOf(strBw);

        GregorianCalendar gcStart = (GregorianCalendar) GregorianCalendar.getInstance();
        String strStartTime = rs.getStartTime();
        XMLGregorianCalendar startTime = null;
        if (strStartTime.equals("now")) {
            startTime = new XMLGregorianCalendarImpl(gcStart);
        } else {
            System.out.println("Only support \"now\" for start time at the moment");
            System.exit(1);
        }

        XMLGregorianCalendar endTime = null;
        GregorianCalendar gcEnd = (GregorianCalendar) GregorianCalendar.getInstance();
        String strEndTime = rs.getEndTime();
        if (strEndTime.startsWith("+")) {
            strEndTime = strEndTime.substring(1);
            Integer duration = Integer.valueOf(strEndTime);
            Long millisStart = gcStart.getTimeInMillis();
            Long millisEnd = millisStart + (duration * 1000);
            gcEnd.setTimeInMillis(millisEnd);
            endTime = new XMLGregorianCalendarImpl(gcEnd);
        } else {
            System.out.println("Only support \"+XXX\" format for end time at the moment");
            System.exit(1);
        }


        for (String rid : rs.getRequestIds()) {
            System.out.println(" Processing request: "+rid);
            Request req = clientConfig.getRequest(rid);
            String vlan = req.getVlan();

            String src = clientConfig.getEndpointInfo(req.getSrc()).getUrn();
            String dst = clientConfig.getEndpointInfo(req.getDst()).getUrn();
            EndpointPair ep = new EndpointPair();

            String serverId = req.getServerId();

            ReservationParams resvParams = new ReservationParams();
            CommonParams cp = new CommonParams();
            TimeParams tp = new TimeParams();
            AvpSeq adhoc = new AvpSeq();
            Avp vlanParam = new Avp();


            cp.setStatus(Status.REQUESTING);
            tp.setRequestedStartTime(startTime);
            tp.setRequestedEndTime(endTime);
            vlanParam.setAttrName("eth.vlan");
            vlanParam.setAttrVal(vlan);
            ep.setA(src);
            ep.setZ(dst);
            cp.setBandwidth(bandwidth);

            resvParams.setReservationId(reservationId);
            resvParams.setEndpoints(ep);
            resvParams.setEndtoendParams(cp);
            cp.setTimeParams(tp);
            cp.setAdhocParams(adhoc);
            adhoc.getAvp().add(vlanParam);
            reservations.put(rid+"-requested", resvParams);
            this.sendCreate(serverId, rid, resvParams);
        }
    }


    private void sendCreate(String serverId, String requestId, ReservationParams resvParams) {
        this.initSoap(serverId);
        Create wrapReq = new Create();
        CreateReq createReq = new CreateReq();
        wrapReq.setArg0(createReq);
        createReq.setResvParams(resvParams);
        try {
            CreateResponse wrapResp = port.create(wrapReq);
            CreateResp createResp = wrapResp.getReturn();
            reservations.put(requestId+"-result", createResp.getResvParams());

        } catch (InternalFault_Exception e) {
            System.out.println("InternalFault has occurred.");
            System.out.println(e.toString());
        } catch (ExternalFault_Exception e) {
            System.out.println("ExternalFault has occurred.");
            System.out.println(e.toString());
        }
    }



    private void list() {
        this.initSoap(serverId);

        is.glif.fenius.ws.draft.gen.List wrapReq = new is.glif.fenius.ws.draft.gen.List();
        ListReq listReq = new ListReq();
        wrapReq.setArg0(listReq);
        try {
            ListResponse resp = port.list(wrapReq);
            List<ReservationParams> resvs = resp.getReturn().getReservations();
            for (ReservationParams resv : resvs) {
                System.out.println("got id: "+resv.getReservationId());
                reservations.put(resv.getReservationId(), resv);
            }

        } catch (InternalFault_Exception e) {
            System.out.println("InternalFault has occurred.");
            e.printStackTrace();
            System.out.println(e.toString());
        } catch (ExternalFault_Exception e) {
            System.out.println("ExternalFault has occurred.");
            e.printStackTrace();
            System.out.println(e.toString());
        }
    }


    private void cancel() {
        System.out.println("Not implemented yet");
    }









    public void setOperation(Operation op) {
        this.operation = op;
    }
    public Operation getOperation() {
        return this.operation;
    }


    public void setRequestSetId(String requestSetId) {
        this.requestSetId = requestSetId;
    }

    public String getRequestSetId() {
        return requestSetId;
    }

    public void setReservationId(String reservationId) {
        this.reservationId = reservationId;
    }

    public String getReservationId() {
        return reservationId;
    }

    public void setServerId(String serverId) {
        this.serverId = serverId;
    }

    public String getServerId() {
        return serverId;
    }


    public HashMap<String, ReservationParams> getReservations() {
        return reservations;
    }


    public void setClientConfig(ClientConfig clientConfig) {
        this.clientConfig = clientConfig;
    }


    public ClientConfig getClientConfig() {
        return clientConfig;
    }


}
